#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import datetime
import json
import locale
import os.path
import platform
import signal
import time
from os import stat

import pexpect

from common.file_common import delete_file
from oracle import logger
from common.common import output_result_file, read_result_file, execute_cmd_list, touch_file
from common.const import ExecuteResultEnum
from common.parse_parafile import ParamFileUtil, get_env_variable
from common.schemas.thrift_base_data_type import ActionResult
from oracle.common.backup_common import get_ip_port_from_address
from oracle.common.common import send_params_to_pexpect, convert_error_code_only, get_oracle_group, \
    get_asm_disk_string, get_disk_group_set, login_asm_instance, check_database_open_mode, \
    check_cdb_is_open, list_pdbs, list_user, list_table_space, chown_file_path_owner, list_all_pdbs
from oracle.common.constants import RootCommand, PluginPathConstant, ListAppQueryConstant, ErrorCode, \
    ScriptExitCode, HostInfoConstants, InstanceOnlineStatus, Platform, InstanceInfo, TaskType, SliceConstant, TimeUnit
from oracle.common.device_manager.constants import ReturnCode
from oracle.common.device_manager.device_info import ProtectGroupInfo
from oracle.common.linux_common import execute_linux_rman_cmd
from oracle.common.lun_common import get_all_lun_wwn_info_in_linux_os, get_lun_info_by_wwn, \
    get_all_disk_num_wwn_infos_in_windows_os, get_disk_uuid, check_storage_device_access, get_asm_group_name
from oracle.common.powershell_service import PowershellService
from oracle.common.security_check import OracleSecurityCheck
from oracle.common.sql_common import get_archive_dest_list, get_first_scn_contains_time, \
    get_last_archive_log_next_change, get_local_listener
from oracle.common.user_env_common import get_asm_user_env_by_linux, get_user_shell_type, \
    get_oracle_user_env_by_linux, update_oracle_home_path, update_oracle_env
from oracle.schemas.oracle_schemas import Resource, QueryClusterResponse, ListAppResponse
from oracle.services.resource.oracle_windows_resource import OracleWindows
from oracle.common.windows_common import validate_low_auth_list, windows_switch_user, \
    windows_switch_user_end, get_low_auth_info_from_env, write_tmp_file, execute_windows_rman_cmd

if platform.system().lower() != "windows":
    from pwd import getpwuid
    from common.util.check_user_utils import check_os_user


class OracleResourceService:
    """
    oracle资源管理服务
    """

    @staticmethod
    def check_application(pid):
        """
        检查数据库连通性 向Agent框架写数据
        :param pid: 请求ID
        :return:
        """
        logger.info(f"Start check application, pid={pid}.")
        response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value)
        response.body_err = ErrorCode.ERROR_COMMON_SCRIPT_EXEC_FAILED.value
        if platform.system().lower() == "windows":
            file_content = ParamFileUtil.parse_param_windows_file(pid)

            # 这里新增的逻辑是1.5遗留的一张问题单，为防止升级到1.6时中断业务，所以预埋了低权限用户信息
            # 此处在资源接入时对该信息做校验，后续1.6需要通过切换低权限用户去调sqlplus，从而避免安全高调低的问题
            # 只有windows操作系统需要切换低权限用户调用sqlplus，不管是资源接入还是备份恢复（对应Linux中的su指令）
            # 一些关于此处逻辑、可能会对1.6有用的函数写在windows_common.py里，函数如下：
            # validate_low_auth_list, get_low_auth_info, windows_switch_user, windows_switch_user_end

            # 确定每个低权限用户名都不为空
            low_auth_user_list = get_low_auth_info_from_env()

            ret = validate_low_auth_list(low_auth_user_list)
            if ret != ScriptExitCode.SUCCESS:
                response.body_err = ErrorCode.ERROR_INVALID_LOW_AUTH_INFO
                output_result_file(pid, response.dict(by_alias=True))
                return
            # 验证低权限用户信息是否正确（用户名密码是否匹配）
            ret, username = windows_switch_user(pid)
            if ret != ScriptExitCode.SUCCESS:
                response.body_err = convert_error_code_only(ScriptExitCode.ERROR_INVALID_LOW_AUTH_INFO)
                output_result_file(pid, response.dict(by_alias=True))
                return
            _ = windows_switch_user_end(username)

        else:
            file_content = ParamFileUtil.parse_param_file(pid)
        # check storage -- just before register oracle check storage
        is_check_storage = file_content['application'].get("extendInfo", "").get("isCheckStorage", "false")
        if is_check_storage == "true":
            err_params, status = OracleResourceService.check_storage_connect(
                pid, file_content['application'].get('auth', None).get('extendInfo', ''))
            if status != ScriptExitCode.SUCCESS:
                response.body_err = convert_error_code_only(status)
                response.body_err_params = err_params
                output_result_file(pid, response.dict(by_alias=True))
                return
            response = ActionResult(code=ExecuteResultEnum.SUCCESS.value)
            output_result_file(pid, response.dict(by_alias=True))
            return
        install_user_name, asm_install_user_name = OracleResourceService.get_db_and_asm_install_user_name(file_content)
        oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
        db_name = file_content['application'].get('name', '')
        db_pwd = get_env_variable(f'application_auth_authPwd_{pid}')
        db_user = get_env_variable(f'application_auth_authKey_{pid}')
        ret_pwd = OracleSecurityCheck.check_database_user_name_and_password(pid, db_pwd)
        ret_user = OracleSecurityCheck.check_database_user_name_and_password(pid, db_user)
        # 参数校验：用户名和密码都得通过参数校验，否则上报错误
        if not (ret_pwd and ret_user):
            response.body_err = convert_error_code_only(ScriptExitCode.ERROR_DB_USERPWD_WRONG)
            output_result_file(pid, response.dict(by_alias=True))
            return
        sub_type = file_content['application'].get("subType", "")
        db_resources, script_exit_code = OracleResourceService.query_database(pid, install_user_name,
                                                                              asm_install_user_name, db_name,
                                                                              db_user=db_user, db_password=db_pwd,
                                                                              oracle_base=oracle_base,
                                                                              oracle_home=oracle_home,
                                                                              sub_type=sub_type)
        if script_exit_code != ScriptExitCode.SUCCESS:
            logger.error(f"query_database, script_exit_code={script_exit_code}.")
            response.body_err = convert_error_code_only(script_exit_code)
            output_result_file(pid, response.dict(by_alias=True))
            return
        db_name = file_content['application'].get('name', '')
        target_resources = OracleResourceService.get_target_database(db_resources, db_name)
        try:
            response = OracleResourceService.check_connection(pid, target_resources, file_content, db_user, db_pwd)
        except Exception as exception:
            logger.error(f"Check connection fail, exception:{exception}, pid={pid}.")
        finally:
            logger.info(f"Get check result, code={response.code},body_err={response.body_err},{pid}")
            output_result_file(pid, response.dict(by_alias=True))

    @staticmethod
    def query_cluster(pid):
        """
        查询集群信息 向Agent框架写数据
        :param pid: 请求ID
        :return:
        """
        logger.info(f"Start query cluster, pid={pid}.")
        response = QueryClusterResponse()
        if platform.system().lower() == "windows":
            file_content = ParamFileUtil.parse_param_windows_file(pid)
        else:
            file_content = ParamFileUtil.parse_param_file(pid)
        db_name = file_content['application'].get('name', '')
        db_pwd = get_env_variable(f'application_auth_authPwd_{pid}')
        db_user = get_env_variable(f'application_auth_authKey_{pid}')

        ret_pwd = OracleSecurityCheck.check_database_user_name_and_password(pid, db_pwd)
        ret_user = OracleSecurityCheck.check_database_user_name_and_password(pid, db_user)
        # 参数校验：用户名和密码都得通过参数校验，否则上报错误
        if not (ret_pwd and ret_user):
            response.body_err = convert_error_code_only(ScriptExitCode.ERROR_DB_USERPWD_WRONG)
            output_result_file(pid, response.dict(by_alias=True))
            return
        install_user_name, asm_install_user_name = OracleResourceService.get_db_and_asm_install_user_name(file_content)
        oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
        update_oracle_env(oracle_base=oracle_base, oracle_home=oracle_home)
        try:
            resources, _ = OracleResourceService.query_database(pid, install_user_name, asm_install_user_name, db_name,
                                                                db_user=db_user, db_password=db_pwd,
                                                                oracle_base=oracle_base,
                                                                oracle_home=oracle_home)
        except Exception as exception:
            logger.error(f"Query database fail, exception={exception}, pid={pid}")
            output_result_file(pid, response.dict(by_alias=True))
            return
        try:
            response = OracleResourceService.get_node_info(pid, resources, file_content, install_user_name,
                                                           asm_install_user_name)
        except Exception as exception:
            logger.error(exception, exc_info=True)
            raise exception
        finally:
            output_result_file(pid, response.dict(by_alias=True))

    @staticmethod
    def get_node_info(pid, resources, file_content, install_user_name, asm_install_user_name):
        """
        筛选返回指定db_name的信息
        :param asm_install_user_name: asm_install_user_name
        :param file_content: file_content
        :param pid: 请求ID
        :param resources: 数据库资源列表
        :param install_user_name: 数据库安装用户名称
        :return:指定的数据库信息
        """
        db_name = file_content['application'].get('name', '')
        response = QueryClusterResponse()
        if not resources:
            logger.info(f"Query no database info, pid={pid}.")
            return response
        instance_name = resources[0].extend_info.get('inst_name', '')
        login_params = {
            'instance_name': instance_name,
            'db_install_user': install_user_name,
            'auth_pwd': f"application_auth_authPwd_{pid}",
            'auth_key': f"application_auth_authKey_{pid}"
        }
        # 适配windows
        if platform.system().lower() == "windows":
            logger.info("Windows get_node_info start!")
            oracle_windows = OracleWindows(pid)
            ret, oracle_ip_info_list = oracle_windows.oracle_get_ip_and_port(login_params)
            if not ret:
                logger.error("Windows oracle_get_ip_and_port FAILED!")
                return response
            logger.info("Windows oracle_get_ip_and_port SUCCESS!")
        else:
            oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
            oracle_ip_info_list = OracleResourceService.query_ip_and_port(pid, login_params, install_user_name,
                                                                          asm_install_user_name, oracle_home)
        target_resource = None
        for resource in resources:
            if resource.extend_info.get('db_name').upper() != db_name.upper():
                continue
            if int(resource.extend_info.get('state')) == InstanceOnlineStatus.INSTANCE_ONLINE:
                target_resource = resource
                break
        response.extend_info = dict()
        if target_resource:
            response.extend_info['status'] = target_resource.extend_info.get('state', None)
            response.extend_info['inst_name'] = target_resource.extend_info.get('inst_name', None)
            response.extend_info['oracle_home'] = target_resource.extend_info.get('oracle_home', None)
            response.extend_info['oracle_base'] = target_resource.extend_info.get('oracle_base', None)
            response.extend_info['is_asm_inst'] = target_resource.extend_info.get('is_asm_inst', None)
            response.extend_info['db_role'] = target_resource.extend_info.get('db_role', None)
            response.extend_info['version'] = target_resource.extend_info.get('version', None)
            response.extend_info['is_cdb'] = target_resource.extend_info.get('is_cdb', None)
            response.extend_info['oracle_ip_infos'] = json.dumps(oracle_ip_info_list)
            response.extend_info['open_mode'] = target_resource.extend_info.get('open_mode', None)
            if platform.system().lower() != "windows":
                response.extend_info['oracle_group'] = get_oracle_group(install_user_name)
            else:
                response.extend_info['oracle_group'] = "oinstall"
        logger.info(f"Query cluster end, pid={pid}.")
        return response

    @staticmethod
    def list_application(pid):
        """
        查询应用详情：可查询数据库列表、asm、rac 向Agent框架写数据
        :param pid: 请求ID
        :return:
        """
        logger.info(f"Start query detail, pid={pid}.")
        response = ListAppResponse()
        # 读取参数文件
        if platform.system().lower() == "windows":
            file_content = ParamFileUtil.parse_param_windows_file(pid)
        else:
            file_content = ParamFileUtil.parse_param_file(pid)
        app_info = file_content['application']
        app_extend_info = app_info['extendInfo']
        query_type = app_extend_info.get('queryType', None)
        install_user_name, asm_install_user_name = OracleResourceService.get_db_and_asm_install_user_name(
            file_content)
        try:
            response = OracleResourceService.list_application_detail(pid, install_user_name, asm_install_user_name,
                                                                     query_type, file_content)
        except Exception as exception:
            logger.error(exception, exc_info=True)
        finally:
            logger.info(f"Query detail end, pid={pid}")
            output_result_file(pid, response.dict(by_alias=True))

    @staticmethod
    def list_application_v2(pid):
        """
        查询应用详情：可查询数据库盘符信息等
        :param pid: 请求ID
        :return:
        """
        logger.info(f"list_application_v2 start, pid={pid}.")
        response = ListAppResponse()
        # 读取参数文件
        if platform.system().lower() == "windows":
            file_content = ParamFileUtil.parse_param_windows_file(pid)
        else:
            file_content = ParamFileUtil.parse_param_file(pid)
        query_type = file_content['condition']['conditions'].get('queryType', '')
        # 获取安装用户名、数据库实例名
        install_user_name = file_content['applications'][0]['extendInfo'].get('installUsername', '')
        logger.info(f"install_user_name={install_user_name}.")
        if file_content['appEnv']['subType'] == ListAppQueryConstant.ORACLE_CLUSTER_ENV:
            extend_info = file_content.get('applications')[0]['extendInfo']
            instance_info = OracleResourceService.get_instance_infos(
                file_content, extend_info)
            db_instance_name = instance_info.inst_name
        else:
            db_instance_name = file_content['applications'][0]['extendInfo'].get('inst_name', '')
        logger.info(f"db_instance_name={db_instance_name}.")
        try:
            response = OracleResourceService.list_application_detail_v2(pid, query_type, file_content,
                                                                        db_instance_name, install_user_name)
        except Exception as exception:
            logger.error(exception, exc_info=True)
        finally:
            logger.info(f"list_application_v2 Query detail end, pid={pid}")
            output_result_file(pid, response.dict(by_alias=True))

    @staticmethod
    def list_application_detail_v2(pid, query_type, file_content, db_instance_name, install_user_name):
        logger.info(f"list_application_detail_v2 start, pid={pid}.")
        oracle_home = ""
        oracle_base = ""
        try:
            oracle_home = (
                    file_content['applications'][0]['extendInfo'].get('accessOracleHome', '')
                    or file_content['applications'][0]['extendInfo'].get('oracle_home', '')
            )
            oracle_base = (
                    file_content['applications'][0]['extendInfo'].get('accessOracleHome', '')
                    or file_content['applications'][0]['extendInfo'].get('oracle_base', '')
            )
        except Exception as exception:
            logger.error(f"Getting oracle_home fail, exception:{exception}, pid={pid}.")
        update_oracle_env(oracle_base=oracle_base, oracle_home=oracle_home)
        response = ListAppResponse()
        if query_type == ListAppQueryConstant.DISKINFO.value:
            resource = OracleResourceService.query_disk_infos(pid, file_content)
            response.resource_list.append(resource)
        elif query_type == ListAppQueryConstant.PDB.value:
            resource = OracleResourceService.query_pdb(pid, db_instance_name, install_user_name)
            response.resource_list.append(resource)
        elif query_type == ListAppQueryConstant.PDB_INFO.value:
            # 如果是CDB数据库：查询当前数据库中的PDB信息、PDB中的表空间信息
            login_params = {
                'instance_name': db_instance_name,
                'db_install_user': install_user_name,
                'auth_pwd': f"application_auth_authPwd_{pid}",
                'auth_key': f"application_auth_authKey_{pid}"
            }
            cbd_is_open = check_cdb_is_open(pid, login_params)
            db_pwd = get_env_variable(f'applications_0_auth_authPwd_{pid}')
            if cbd_is_open:
                resource = OracleResourceService.query_pdb_infos(pid, db_instance_name, install_user_name)
                response.resource_list.append(resource)
            # 如果不是CDB数据库：查询当前数据库中的表空间和表信息
            else:
                # 查询表空间下所有的用户
                resource = OracleResourceService.query_table_infos(pid, db_instance_name, install_user_name, "")
                response.resource_list.append(resource)
        else:
            logger.info(f"Check query_type not right, query_type={query_type}, pid={pid}.")
        return response

    @staticmethod
    def query_pdb(pid, db_instance_name, install_user_name):
        logger.info(f"query_pdb, pid {pid}")
        resource = Resource()
        pdb_infos = list_all_pdbs(pid, db_instance_name, install_user_name, TaskType.RESOURCE_SERVICE)
        pdb_results = []
        for pdb_info in pdb_infos:
            pdb_results.append(pdb_info.get("NAME"))
        resource.extend_info = dict()
        result = {
            "is_cdb": True,
            "pdb_names": pdb_results,
            "pdb_status": True if pdb_infos else False
        }
        resource.extend_info['pdb'] = json.dumps(result)
        logger.info(f"query_pdb, pdb: {result}")
        return resource

    @staticmethod
    def query_pdb_infos(pid, db_instance_name, install_user_name):
        logger.info(f"query_pdb_infos, pid {pid}")
        resource = Resource()
        pdb_infos = list_pdbs(pid, db_instance_name, install_user_name, TaskType.RESOURCE_SERVICE)

        pdb_results = []
        for pdb_info in pdb_infos:
            pdb_name = pdb_info.get("NAME")
            user_names = []
            query_results = list_user(pid, db_instance_name, install_user_name, pdb_name, TaskType.RESOURCE_SERVICE)
            for query_result in query_results:
                user_name = query_result.get("USERNAME")
                if user_name:
                    user_names.append(user_name)

            table_spaces = []
            query_results = list_table_space(pid, db_instance_name, install_user_name, pdb_name)
            for query_result in query_results:
                table_space = query_result.get("TABLESPACE_NAME")
                if table_space:
                    table_spaces.append(table_space)
            pdb_results.append({
                "pdb_name": pdb_name,
                "user_names": user_names,
                "table_spaces": table_spaces
            })

        # 转成user:pdb->table_spaces的形式
        users_map = {}
        for pdb_result in pdb_results:
            user_names = pdb_result.get("user_names")
            for user_name in user_names:
                OracleResourceService.merge_pdbs(users_map, user_name, pdb_result)

        resource.extend_info = dict()
        result = {
            "is_cdb": True,
            "users": list(users_map.values()),
            "pdb_status": True if pdb_infos else False
        }
        resource.extend_info['table_space_infos'] = json.dumps(result)
        logger.info(f"query_pdb_infos, table_space_infos {result}")
        return resource

    @staticmethod
    def query_table_infos(pid, db_instance_name, install_user_name, pdb_name):
        logger.info(f"query_table_space_infos, pid {pid}")
        resource = Resource()
        user_names = []
        query_results = list_user(pid, db_instance_name, install_user_name, "", TaskType.RESOURCE_SERVICE)
        for query_result in query_results:
            user_name = query_result.get("USERNAME")
            if user_name:
                user_names.append(user_name)
        table_spaces = []
        query_results = list_table_space(pid, db_instance_name, install_user_name, "")
        for query_result in query_results:
            table_space = query_result.get("TABLESPACE_NAME")
            if table_space:
                table_spaces.append(table_space)

        resource.extend_info = dict()
        result = {
            "is_cdb": False,
            "user_names": user_names,
            "table_spaces": table_spaces
        }
        resource.extend_info['table_space_infos'] = json.dumps(result)
        logger.info(f"query_table_space_infos, table_space_infos {result}")
        return resource

    @staticmethod
    def query_disk_infos(pid, file_content):
        logger.info(f"query_disk_infos, pid {pid}")
        resource = Resource()
        application = file_content.get('applications')[0]
        extend_info = application['extendInfo']
        is_asm = False
        install_user_name = ''
        if file_content['appEnv']['subType'] == ListAppQueryConstant.ORACLE_CLUSTER_ENV:
            instance_info = OracleResourceService.get_instance_infos(
                file_content, extend_info)
            is_asm = instance_info.is_asm
            install_user_name = instance_info.install_user_name
        else:
            install_user_name = extend_info.get('installUsername')
            if extend_info.get('is_asm_inst', None) == '1':
                is_asm = True
        storages = json.loads(application['auth']['extendInfo'].get('storages', []))
        device_manager = None
        access_storage = None
        for index, storage in enumerate(storages):
            password = json.loads(get_env_variable(f"applications_0_auth_extendInfo_storagesPwd_{pid}"))[index]
            ret, device_manager = check_storage_device_access(storage, password, pid)
            access_storage = storage
            if ret:
                logger.info(f"query_disk_infos access_storage, {access_storage.get('ipList')}")
                break
        if not device_manager:
            logger.error(f"query_disk_infos, device_manager null")
            return resource
        if platform.system().lower() == "windows":
            disk_infos = OracleResourceService.get_disk_info_in_windows_os(device_manager)
        else:
            disk_infos = OracleResourceService.get_disk_info_in_linux_os(access_storage, device_manager, is_asm,
                                                                         install_user_name)
        resource.extend_info = dict()
        resource.extend_info['disk_infos'] = json.dumps(disk_infos)
        logger.info(f"query_disk_infos, disk_infos {disk_infos}")
        return resource

    @staticmethod
    def get_instance_infos(file_content, extend_info):
        is_asm = False
        inst_name = ''
        install_user_name = ''
        asm_inst_name = ''
        asm_install_user_name = ''
        host_id = file_content['condition']['conditions']['hostId']
        instances = json.loads(extend_info.get('instances', '{}'))
        for instance in instances:
            if instance.get('hostId') == host_id:
                inst_name = instance.get('inst_name', '')
                install_user_name = instance.get('installUsername', '')
                if instance.get('is_asm_inst', None) == '1':
                    asm_info = json.loads(instance.get('asm_info', {}))
                    asm_inst_name = asm_info.get('instName', '')
                    asm_install_user_name = asm_info.get('installUsername', '')
                    is_asm = True
                break
        instance_info = InstanceInfo(is_asm, inst_name, install_user_name, asm_inst_name, asm_install_user_name)
        return instance_info

    @staticmethod
    def list_application_detail(pid, install_user_name, asm_install_user_name, query_type, file_content):
        """
        根据查询类型调用查询方法
        :param pid: 请求ID
        :param install_user_name: 数据库安装用户名
        :param asm_install_user_name: asm安装用户名
        :param query_type: 查询类型
        :param file_content: 查询参数
        :return:查询到的信息
        """
        response = ListAppResponse()
        db_name = file_content['application'].get('name', '')
        db_pwd = get_env_variable(f'application_auth_authPwd_{pid}')
        db_user = get_env_variable(f'application_auth_authKey_{pid}')

        ret_pwd = OracleSecurityCheck.check_database_user_name_and_password(pid, db_pwd)
        ret_user = OracleSecurityCheck.check_database_user_name_and_password(pid, db_user)
        oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
        # 参数校验：用户名和密码都得通过参数校验，否则上报错误
        if not (ret_pwd and ret_user):
            response.body_err = convert_error_code_only(ScriptExitCode.ERROR_DB_USERPWD_WRONG)
            output_result_file(pid, response.dict(by_alias=True))
            return response

        # 根据查询类型 调用查询方法
        if query_type == ListAppQueryConstant.DATABASE.value:
            logger.info(f"Start list application -- database, pid={pid}.")
            response.resource_list, _ = OracleResourceService.query_database(pid, install_user_name,
                                                                             asm_install_user_name, db_name,
                                                                             db_user=db_user, db_password=db_pwd,
                                                                             oracle_base=oracle_base,
                                                                             oracle_home=oracle_home)
        elif query_type == ListAppQueryConstant.ASM.value:
            logger.info(f"Start list application -- asm, pid={pid}.")
            response.resource_list = OracleResourceService.query_asm_instance(pid, install_user_name,
                                                                              asm_install_user_name, file_content)
        elif query_type == ListAppQueryConstant.RAC.value:
            logger.info(f"Start list application -- rac, pid={pid}.")
            resource = OracleResourceService.query_rac_info(pid, install_user_name, asm_install_user_name, file_content)
            response.resource_list.append(resource)
        else:
            logger.info(f"Check query_type not right, query_type={query_type}, pid={pid}.")
        return response

    @staticmethod
    def query_ip_and_port(pid, login_params, install_user_name, asm_install_user_name, oracle_home):
        """
        查询ip及端口信息，用于后续备份
        :param pid: 请求ID
        :param login_params: 登录参数
        :param install_user_name: oracle安装用户
        :param asm_install_user_name: asm用户
        :param oracle_home 页面输入的oracle_home
        :return: 监听的端口和ip
        """
        oracle_ip_info_list = []
        local_listener = get_local_listener(pid, login_params)
        if 'ADDRESS=' in local_listener:
            ip_info, port = get_ip_port_from_address(local_listener)
            oracle_ip_info_list.append({'ip': ip_info, 'port': port})
            logger.info(f"Execute oracle_get_ip_and_port SUCCESS, ip_port_list={oracle_ip_info_list}, pid={pid}.")
            return oracle_ip_info_list
        if not asm_install_user_name:
            asm_install_user_name = "grid"
        asm_user_env = get_asm_user_env_by_linux(asm_install_user_name)
        oracle_ip_info_list = OracleResourceService.get_ip_and_port_from_lsnrctl_cmd(asm_install_user_name,
                                                                                     asm_user_env, local_listener)
        if oracle_ip_info_list:
            logger.info(f"oracle_ip_info_list={oracle_ip_info_list}, pid={pid}")
            return oracle_ip_info_list
        export_oracle_home = ''
        if oracle_home:
            get_user_shell_type(install_user_name)
            update_oracle_home_path(oracle_home)
            export_oracle_home = get_oracle_user_env_by_linux()
        oracle_ip_info_list = OracleResourceService.get_ip_and_port_from_lsnrctl_cmd(install_user_name,
                                                                                     export_oracle_home, local_listener)
        logger.info(f"oracle_ip_info_list={oracle_ip_info_list}, pid={pid}")
        return oracle_ip_info_list

    @staticmethod
    def get_ip_and_port_from_lsnrctl_cmd(user: str, user_env: str, local_listener: str):
        oracle_ip_info_list = []
        for alias in local_listener.split(","):
            cmd_list = [f"su - {user} -c '{user_env}lsnrctl status {alias}'", "grep PROTOCOL=tcp"]
            return_code, ip_str_list, err_str = execute_cmd_list(cmd_list)
            logger.info(f"cmd_list:{cmd_list},ip_str_list:{ip_str_list},err_str:{err_str}")
            if int(return_code) != ScriptExitCode.SUCCESS:
                logger.warn(f"get_ip_and_port_from_lsnrctl_cmd:{cmd_list}")
                continue
            ip_str_list = ip_str_list.splitlines()
            for ip_str in ip_str_list:
                ip_info, port = get_ip_port_from_address(ip_str)
                if not ip_info or not port:
                    continue
                oracle_ip_info_list.append({'ip': ip_info, 'port': port})
        return oracle_ip_info_list

    @staticmethod
    def check_install_user_name(pid, install_user_name, inst_name):
        """
        校验数据库安装用户名
        :param pid: 请求ID
        :param install_user_name: 数据库安装用户名
        :param inst_name: 实例名
        :return:
        """
        response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR)
        response.body_err = ErrorCode.ERROR_COMMON_INVALID_PARAM.value
        if platform.system().lower() == "windows":
            return True, ActionResult(code=ExecuteResultEnum.SUCCESS)
        if not check_os_user(install_user_name):
            logger.error(f"Not exist {install_user_name} os username.")
            return False, response
        cmd_list = ["ps -ef", "grep pmon", f"grep {install_user_name}", f"grep {inst_name}"]
        return_code, output, err_str = execute_cmd_list(cmd_list)
        logger.info(f"cmd_list= {cmd_list}, return_code={return_code},output={output},err_str={err_str}")
        if int(return_code) != ExecuteResultEnum.SUCCESS.value:
            logger.error(f"grep install_user_name failed, cmd:{cmd_list}, "
                         f"output={output},err_str={err_str}, pid={pid}")
            return False, response
        return True, ActionResult(code=ExecuteResultEnum.SUCCESS)

    @staticmethod
    def check_asm_install_user_name(pid, asm_install_user_name):
        """
        校验ASM安装用户名
        :param pid: 请求ID
        :param asm_install_user_name: 数据库安装用户名
        :return:
        """
        # 未开启asm认证 则直接返回成功
        if not asm_install_user_name:
            return True, ActionResult(code=ExecuteResultEnum.SUCCESS)
        response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR)
        response.body_err = ErrorCode.ERROR_COMMON_INVALID_PARAM.value
        # 查询该目录下是否有与asm安装用户名名对应的目录 有则校验有无和asm安装用户名相等的目录名
        # 适配windows
        if platform.system().lower() == "windows":
            return True, ActionResult(code=ExecuteResultEnum.SUCCESS)
        else:
            cmd_list = [f"cat /etc/oracle/olr.loc", f"grep 'crs_home'"]
            return_code, crs_home_path, err_str = execute_cmd_list(cmd_list)
        logger.info(f"return_code={return_code},crs_home_path={crs_home_path},err_str={err_str}")
        if int(return_code) != ExecuteResultEnum.SUCCESS.value:
            logger.error(
                f"Get no dir_name match db_name, cmd:{cmd_list}, "
                f"return_code:{return_code}, err_str:{err_str}, pid={pid}")
            return False, response
        crs_home_path = crs_home_path.split('=')[1]
        crs_home_path = crs_home_path.strip()
        try:
            ora_grid_user = getpwuid(stat(f"{crs_home_path}/bin/oracle").st_uid).pw_name
        except Exception as exception:
            logger.error(f"exception={exception}, pid={pid}")
            raise exception
        if ora_grid_user == asm_install_user_name:
            logger.info(f"Check asm_install_user_name right, pid={pid}")
            return True, ActionResult(code=ExecuteResultEnum.SUCCESS)
        logger.error(
            f"Check asm_install_name not right, cmd:{cmd_list}, "
            f"return_code:{return_code}, err_str:{err_str}, pid={pid}")
        response.body_err = ErrorCode.ERROR_ORACLE_AUTH_INFO_FAILED.value
        return False, response

    @staticmethod
    def check_params_for_connection(pid, resources, file_content):
        """
        查询应用详情：获取并校验参数，包装为字典
        :param pid: 请求ID
        :param resources: 数据库资源信息列表
        :param file_content: 参数文件信息
        :return: 元组 (传给shell脚本的参数字典,错误信息)
        """
        params = {}
        response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR)
        if not resources:
            response.body_err = ErrorCode.ERROR_COMMON_INVALID_PARAM.value
            logger.info(f"Database name may not right, pid={pid}")
            return params, response
        install_user_name, asm_install_user_name = OracleResourceService.get_db_and_asm_install_user_name(file_content)
        logger.info(
            f"Get database name={resources[0].extend_info.get('db_name')},installName={install_user_name} pid={pid}.")
        auth_key, auth_pwd, auth_type = OracleResourceService.get_auth_params(pid)
        instance_name, app_name, oracle_home = '', '', ''
        for resource in resources:
            if int(resource.extend_info.get('state')) != InstanceOnlineStatus.INSTANCE_ONLINE:
                continue
            instance_name = resource.extend_info.get('inst_name')
            app_name = resource.extend_info.get('db_name')
            oracle_home = resource.extend_info.get('oracle_home')
            break
        if not app_name:
            logger.info(f"No database instance online, pid={pid}")
            response.body_err = ErrorCode.DATABASE_OFFLINE_ERR.value
            return params, response
        try:
            oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
            flag_database, response_db = OracleResourceService.check_install_user_name(pid, install_user_name,
                                                                                       instance_name)
        except Exception as exception:
            response.body_err = ErrorCode.ERROR_COMMON_SCRIPT_EXEC_FAILED.value
            logger.error(exception, exc_info=True)
            return params, response
        try:
            flag_asm, response_asm = OracleResourceService.check_asm_install_user_name(pid, asm_install_user_name)
        except Exception as exception:
            response.body_err = ErrorCode.ERROR_COMMON_SCRIPT_EXEC_FAILED.value
            logger.error(exception, exc_info=True)
            return params, response
        if not flag_database or not flag_asm:
            if flag_database:
                return params, response_asm
            return params, response_db
        logger.info(f"Get instance_name={instance_name} app_name={app_name} oracle_home={oracle_home}, pid={pid}.")
        params = OracleResourceService.general_params_for_connection_test(instance_name, app_name, install_user_name,
                                                                          asm_install_user_name,
                                                                          auth_key=auth_key, auth_pwd=auth_pwd,
                                                                          oracle_base=oracle_base,
                                                                          oracle_home=oracle_home)
        return params, ActionResult(code=ExecuteResultEnum.SUCCESS.value)

    @staticmethod
    def general_params_for_connection_test(instance_name, app_name, install_user_name, asm_install_user_name, **kwargs):
        auth_key = kwargs.get('auth_key')
        auth_pwd = kwargs.get('auth_pwd')
        return {
            'InstanceName': instance_name,
            'AppName': app_name,
            'UserName': auth_key,
            'Password': auth_pwd,
            'OracleInstallUser': install_user_name,
            'GridInstallUser': asm_install_user_name,
            'AccessOracleBase': kwargs.get('oracle_base'),
            'AccessOracleHome': kwargs.get('oracle_home')
        }

    @staticmethod
    def get_auth_params(pid):
        """
        获取数据库用户名密码、数据库认证方式
        :param pid: 请求ID
        :return:
        """
        auth_key = get_env_variable(f'application_auth_authKey_{pid}')
        auth_pwd = get_env_variable(f'application_auth_authPwd_{pid}')
        auth_type = get_env_variable(f'application_auth_authType_{pid}')
        return auth_key, auth_pwd, auth_type

    @staticmethod
    def get_db_and_asm_install_user_name(file_content):
        """
        获取数据库安装用户名和asm安装用户名
        :param file_content: 参数文件内容
        :return:
        """
        app_info = file_content['application']
        asm_info = json.loads(app_info['auth']['extendInfo'].get('asmInfo', '{}'))
        asm_install_user_name = asm_info.get('installUsername', '')
        app_extend_info = app_info['extendInfo']
        install_user_name = app_extend_info.get('installUsername', '')
        if not install_user_name:
            install_user_name = "oracle"
        return install_user_name, asm_install_user_name

    @staticmethod
    def get_oracle_base_and_home(file_content):
        """
        获取数据库oracle_base和oracle_home
        :param file_content: 参数文件内容
        :return:
        """
        app_info = file_content['application']
        app_extend_info = app_info['extendInfo']
        oracle_home = app_extend_info.get('accessOracleHome', '')
        oracle_base = app_extend_info.get('accessOracleBase', '')
        return oracle_base, oracle_home

    @staticmethod
    def check_connection(pid, resources, file_content, db_user="", db_pwd=""):
        """
        执行连通性测试脚本
        :param pid: 请求ID
        :param resources: 数据库资源信息列表
        :param file_content: 参数文件信息
        :param db_user: 数据库用户名
        :param db_pwd: 数据库用户密码
        :return: 执行结果 ActionResult
        """
        if not resources:
            logger.error(f"Query no database info, pid={pid}.")
            response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value)
            response.body_err = ErrorCode.ERROR_ORACLE_AUTH_INFO_FAILED.value
            return response
        params, response = OracleResourceService.check_params_for_connection(pid, resources, file_content)
        if not params:
            return response
        logger.info(f"Start check connection, pid={pid}.")

        # 适配windows
        if platform.system().lower() == "windows":
            logger.info("Windows check_connection start!")
            oracle_windows = OracleWindows(pid, db_user, db_pwd)
            ret = oracle_windows.oracle_test_connection(params)
            if ret != ScriptExitCode.SUCCESS:
                response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR)
                response.body_err = convert_error_code_only(ret)
                logger.info(f"Windows Test connect fail, pid={pid}.")
                return response
            logger.info(f"Windows Test connect success, pid={pid}.")
            return response

        # sh xxx.sh {oracle插件目录} {pid} {shell脚本需要读取的参数数量--对应sendline输入的参数个数}
        child = pexpect.spawn(
            f"sh {PluginPathConstant.SCRIPTS_PATH.value}/{RootCommand.ROOT_COMMAND_SCRIPT_TEST_ORACLE.value} "
            f"{PluginPathConstant.SCRIPTS_PATH.value} {pid} {len(params)}",
            encoding='utf-8', timeout=None)
        send_params_to_pexpect(params, child)
        child.expect(pexpect.EOF)
        child.close()
        logger.info(f"After sh oracletest.sh CommandReturnCode={child.exitstatus}, pid={pid}.")
        if child.exitstatus != ScriptExitCode.SUCCESS:
            response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR)
            response.body_err = convert_error_code_only(child.exitstatus)
            logger.error(f"Test connect fail, pid={pid}.")
            return response
        logger.info(f"Test connect success, pid={pid}.")
        return response

    @staticmethod
    def query_database(pid, ora_db_user, ora_grid_user, db_name='', **kwargs):
        """
        查询数据库列表
        :param pid: 请求ID
        :param ora_db_user: 数据库安装用户名
        :param ora_grid_user: asm安装用户名
        :param db_name: 数据库名称
        :param db_user: 数据库用户名
        :param db_password: 数据库密码
        :return: 数据库列表
        """
        db_user = kwargs.get('db_user')
        db_password = kwargs.get('db_password')
        logger.info(f"Start query database, pid={pid}.")
        resources = []
        params = {
            'OracleInstallUser': ora_db_user,
            'GridInstallUser': ora_grid_user,
            'AppName': db_name,
            'User': db_user,
            'Password': db_password,
            'AccessOracleBase': kwargs.get('oracle_base'),
            'AccessOracleHome': kwargs.get('oracle_home'),
            'SubType': kwargs.get('sub_type')
        }

        # 适配windows
        if platform.system().lower() == "windows":
            logger.info("Windows query_database start!")
            oracle_windows = OracleWindows(pid, db_user, db_password)
            ret, databases_info = oracle_windows.oracle_info(db_name)
            if ret != ScriptExitCode.SUCCESS:
                logger.error(f"Windows query_database FAILED! ErrorCode = {ret}")
                return resources, ret
            for db_info in databases_info:
                resource = OracleResourceService.get_resource(db_info)
                OracleResourceService.check_database_open_mode_windows(pid, resource, ora_db_user)
                OracleResourceService.check_cdb_pdb_windows(pid, resource, ora_db_user)
                resources.append(resource)
            logger.info("Windows query_database SUCCESS!")
            return resources, ret
        child = pexpect.spawn(
            f"sh {PluginPathConstant.SCRIPTS_PATH.value}/{RootCommand.ROOT_COMMAND_SCRIPT_QUERY_ORACLE_INFO.value}"
            f" {PluginPathConstant.SCRIPTS_PATH.value} {pid} {len(params)}", encoding='utf-8', timeout=180)
        send_params_to_pexpect(params, child)
        index = child.expect([pexpect.TIMEOUT, pexpect.EOF])
        if index == 0:
            child.close()
            logger.error(f"Query_database timeout,child.exitstatus={child.exitstatus} pid={pid}.")
            return resources, child.exitstatus
        child.close()
        logger.info(f"Query_database end, child.exitstatus={child.exitstatus}, pid={pid}.")
        if child.exitstatus != ScriptExitCode.SUCCESS:
            return resources, child.exitstatus
        # 获取脚本的临时result_file里的结果
        update_oracle_env(oracle_home=kwargs.get('oracle_home'), oracle_base=kwargs.get('oracle_base'))
        result_file_name = os.path.join(f'{PluginPathConstant.STMP_PATH.value}',
                                        f'{PluginPathConstant.RESULT_TMP_FILE_PREFIX.value}{pid}')
        OracleResourceService.build_resources(pid, resources, result_file_name)
        # 确认数据库是否为启用CDB
        OracleResourceService.check_cdb_pdb(pid, resources, ora_db_user)
        OracleResourceService.check_database_open_mode(pid, resources, ora_db_user)
        return resources, child.exitstatus

    @staticmethod
    def get_resource(db_info):
        resource = Resource()
        resource.extend_info = dict()
        resource.extend_info['version'] = db_info["version"]
        resource.extend_info['inst_name'] = db_info["inst_name"]
        resource.extend_info['db_name'] = db_info["db_name"]
        resource.extend_info['state'] = db_info["state"]
        resource.extend_info['is_asm_inst'] = db_info["is_asm_inst"]
        resource.extend_info['auth_type'] = db_info["auth_type"]
        resource.extend_info['db_role'] = db_info["db_role"]
        resource.extend_info['oracle_home'] = db_info["oracle_home"]
        resource.extend_info['oracle_base'] = db_info["oracle_base"]
        return resource

    @staticmethod
    def build_resources(pid, resources, result_file_name):
        result = ""
        if os.path.exists(result_file_name):
            if platform.system().lower() == "windows":
                encoding = locale.getdefaultlocale()[1]
                result = read_result_file(result_file_name, encoding=encoding)
            else:
                result = read_result_file(result_file_name)
            os.remove(result_file_name)
            logger.info(f"Delete tmp result file after read file_name={result_file_name}, pid={pid}.")
        for row in result.splitlines():
            resource = Resource()
            resource.extend_info = dict()
            columns = row.split(';')
            resource.extend_info['version'] = columns[0]
            resource.extend_info['inst_name'] = columns[1]
            resource.extend_info['db_name'] = columns[2]
            resource.extend_info['state'] = columns[3]
            resource.extend_info['is_asm_inst'] = columns[4]
            resource.extend_info['auth_type'] = columns[5]
            resource.extend_info['db_role'] = columns[6]
            resource.extend_info['oracle_home'] = columns[7]
            resource.extend_info['oracle_base'] = columns[8]
            resources.append(resource)

    @staticmethod
    def get_target_database(resources, db_name):
        target_db_list = []
        for resource in resources:
            if resource.extend_info['db_name'].upper() != db_name.upper():
                continue
            target_db_list.append(resource)
        return target_db_list

    @staticmethod
    def query_asm_instance(pid, ora_db_user, ora_grid_user, file_content):
        """
        查询ASM实例信息
        :param pid: 请求ID
        :param ora_db_user: 数据库安装用户名
        :param ora_grid_user: asm安装用户名
        :param db_user: 数据库用户名
        :param db_pwd: 数据库用户密码
        :return: ASM实例信息列表
        """
        logger.info(f"Start query asm instance, pid={pid}.")
        resources = []

        # 适配windows
        if platform.system().lower() == "windows":
            logger.info("Windows query_asm_instance start!")
            oracle_windows = OracleWindows(pid, "", "")
            ret, asm_info_list = oracle_windows.oracle_asm_info()
            if ret != ExecuteResultEnum.SUCCESS.value:
                logger.error(f"Windows query_asm_instance FAILED!")
                return resources
            for asm_info in asm_info_list:
                resource = Resource()
                resource.extend_info = dict()
                resource.extend_info['inst_name'] = asm_info["inst_name"]
                resource.extend_info['auth_type'] = asm_info["auth_type"]
                resource.extend_info['is_cluster'] = asm_info["is_cluster"]
                resource = OracleResourceService.try_get_asm_parameters(resource, pid, asm_info["inst_name"],
                                                                        ora_grid_user)
                resources.append(resource)
                logger.info(f"inst_name={asm_info['inst_name']}, "
                            f"axt_type={asm_info['auth_type']}, is_cluster={asm_info['is_cluster']}, pid={pid}.")
            logger.info("Windows query_asm_instance SUCCESS!")
            return resources
        return OracleResourceService.query_asm_instance_in_linux_os(pid, ora_db_user, ora_grid_user, resources,
                                                                    file_content)

    @staticmethod
    def query_asm_instance_in_linux_os(pid, ora_db_user, ora_grid_user, resources, file_content):
        logger.info("linux query_asm_instance start!")
        oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
        params = {
            'OracleInstallUser': ora_db_user,
            'GridInstallUser': ora_grid_user,
            'AccessOracleBase': oracle_base,
            'AccessOracleHome': oracle_home
        }
        # 调用脚本
        child = pexpect.spawn(
            f"sh {PluginPathConstant.SCRIPTS_PATH.value}/{RootCommand.ROOT_COMMAND_SCRIPT_QUERY_ORACLE_ASM.value}"
            f" {PluginPathConstant.SCRIPTS_PATH.value} {pid} {len(params)}",
            encoding='utf-8', timeout=None)
        send_params_to_pexpect(params, child)
        logger.info(f"params={params}")
        child.expect(pexpect.EOF)
        child.close()
        if child.exitstatus != ExecuteResultEnum.SUCCESS.value:
            logger.error(f"Query_asm_instance fail child.exitstatus = {child.exitstatus}, pid={pid}.")
            return resources
        logger.info(f"Query_asm_instance success, pid={pid}.")
        # 获取脚本的临时result_file里的结果
        result_file_name = os.path.join(f'{PluginPathConstant.STMP_PATH.value}',
                                        f'{PluginPathConstant.RESULT_TMP_FILE_PREFIX.value}{pid}')
        result = ""
        if os.path.exists(result_file_name):
            if platform.system().lower() == "windows":
                encoding = locale.getdefaultlocale()[1]
                result = read_result_file(result_file_name, encoding=encoding)
            else:
                result = read_result_file(result_file_name)
            os.remove(result_file_name)
            logger.info(f"Delete tmp result file after read file_name={result_file_name}, pid={pid}.")
        # 处理结果
        if len(result) == 0:
            logger.info(f"Query asm instance result is empty, pid={pid}")
            resources = []
        for row in result.splitlines():
            resource = Resource()
            resource.extend_info = dict()
            columns = row.split(';')
            resource.extend_info['inst_name'] = columns[0]
            resource.extend_info['auth_type'] = columns[1]
            resource.extend_info['is_cluster'] = columns[2]
            resource = OracleResourceService.try_get_asm_parameters(resource, pid, columns[0], ora_grid_user)
            logger.info(f"Query asm instance result , resource={resource}")
            resources.append(resource)
            logger.info(f"inst_name={columns[0]}, axt_type={columns[1]}, is_cluster={columns[2]}, pid={pid}.")
        return resources

    @staticmethod
    def try_get_asm_parameters(resource, pid, asm_instance_name, asm_install_user_name):
        logger.info(f"try_get_asm_parameters start.")
        if not asm_install_user_name:
            asm_install_user_name = 'grid'
        result, child = login_asm_instance(pid, asm_instance_name, asm_install_user_name, timeout=30)
        if not result:
            raise Exception(f'Login asm failed.')
        try:
            OracleResourceService.get_asm_parameters(resource, pid, child)
        except Exception as exception:
            logger.error(f"exception={exception}, pid={pid}")
            raise exception
        finally:
            logger.info(f"try_get_asm_parameters finally.")
            if platform.system().lower() == Platform.WINDOWS:
                child.kill(signal.SIGTERM)
            else:
                child.close()
        return resource

    @staticmethod
    def get_asm_parameters(resource, pid, child):
        asm_disk_string = get_asm_disk_string(pid, child)
        disk_groups = get_disk_group_set(pid, child)
        resource.extend_info['disk_string'] = asm_disk_string
        resource.extend_info['disk_groups'] = json.dumps(list(disk_groups))
        return resource

    @staticmethod
    def query_rac_info(pid, ora_db_user, ora_grid_user, file_content):
        """
        查询数据库RAC集群信息
        :param pid: 请求ID
        :param ora_db_user: 数据库安装用户名
        :param ora_grid_user: asm安装用户名
        :return: RAC信息
        """
        logger.info(f"Start query rac info, pid={pid}.")
        response = Resource()
        oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
        params = {
            'OracleInstallUser': ora_db_user,
            'GridInstallUser': ora_grid_user,
            'AccessOracleBase': oracle_base,
            'AccessOracleHome': oracle_home
        }

        # 适配windows
        if platform.system().lower() == "windows":
            output = OracleResourceService.query_windows_rac_info(pid, "", "", response)
            return output

        # 调用脚本 脚本路径获取
        child = pexpect.spawn(
            f"sh {PluginPathConstant.SCRIPTS_PATH.value}/{RootCommand.ROOT_COMMAND_SCRIPT_QUERY_RAC_INFO.value}"
            f" {PluginPathConstant.SCRIPTS_PATH.value} {pid} {len(params)}",
            encoding='utf-8', timeout=None)
        send_params_to_pexpect(params, child)
        child.expect(pexpect.EOF)
        child.close()
        if child.exitstatus != ExecuteResultEnum.SUCCESS.value:
            return Resource()
        logger.info(f"Query rac success, pid={pid}.")
        # 获取脚本的临时result_file里的结果
        result_file_name = os.path.join(f'{PluginPathConstant.STMP_PATH.value}',
                                        f'{PluginPathConstant.RESULT_TMP_FILE_PREFIX.value}{pid}')
        if platform.system().lower() == "windows":
            encoding = locale.getdefaultlocale()[1]
            result = read_result_file(result_file_name, encoding=encoding)
        else:
            result = read_result_file(result_file_name)
        os.remove(result_file_name)
        logger.info(f"Delete tmp result file after read file_name={result_file_name}, pid={pid}.")
        if len(result) == 0:
            logger.info(f"Query rac info result is empty, pid={pid}")
            return response
        response.extend_info = dict()
        response.extend_info['domain_name'] = []
        for row in result.splitlines():
            columns = row.split(';')
            if columns[0] == 'ClusterType':
                response.extend_info['cluster_type'] = columns[1]
            elif columns[0] == 'ClusterIP':
                response.extend_info['cluster_ip'] = columns[1]
            elif columns[0] == 'ClusterName':
                response.extend_info['cluster_name'] = columns[1]
            elif columns[0] == 'DomainName':
                response.extend_info['domain_name'].append(columns[1])
        logger.info(f"Query rac info end, pid={pid}.")
        response.extend_info['domain_name'] = json.dumps(response.extend_info['domain_name'])
        return response

    @staticmethod
    def query_windows_rac_info(pid, db_user, db_pwd, response):
        logger.info("Windows query_rac_info start!")
        oracle_windows = OracleWindows(pid, db_user, db_pwd)
        ret, rac_info = oracle_windows.oracle_rac_info()
        if ret != ExecuteResultEnum.SUCCESS.value:
            logger.error("Windows query_rac_info FAILED!")
            return Resource()
        response.extend_info = dict()
        response.extend_info['cluster_type'] = rac_info["cluster_type"]
        response.extend_info['cluster_ip'] = rac_info["cluster_ip"]
        response.extend_info['cluster_name'] = rac_info["cluster_name"]
        response.extend_info['domain_name'] = rac_info["domain_name"]
        logger.info("Windows query_rac_info SUCCESS!")
        return response

    @staticmethod
    def check_storage_connect(pid, params):
        """
        检查存储设备的连通性
        :param params: 参数信息
        """
        storages = params.get('storages', [])
        err_params = []
        err_code = ScriptExitCode.ERROR_STORAGE_AUTH_INFO_FAILED
        if not storages:
            logger.info("Storages parameter is empty!")
            return err_params, ScriptExitCode.SUCCESS
        storages = json.loads(storages)
        for index, storage in enumerate(storages):
            password = json.loads(get_env_variable(f"application_auth_extendInfo_storagesPwd_{pid}"))[index]
            logger.info(f"check_storage_connect {index} password {password}")
            # 校验连通性
            ret, device = check_storage_device_access(storage, password, pid)
            if ret:
                logger.info("storage connect success.")
                return err_params, ScriptExitCode.SUCCESS
            else:
                err_code = device.error_code if device and device.error_code else err_code
                logger.error(f"storage connect fail, error code is {err_code}")
        return err_params, err_code

    @staticmethod
    def get_disk_info_in_windows_os(device_manager):
        logger.info(f"get_disk_info_in_windows_os")
        disk_wwn_infos = get_all_disk_num_wwn_infos_in_windows_os()
        disk_infos = []

        asm_disk_infos = OracleResourceService.get_disk_info_by_asm_tool_in_windows_os()

        for disk_wwn_info in disk_wwn_infos:
            lun_info = get_lun_info_by_wwn(disk_wwn_info.wwn, device_manager)
            if not lun_info:
                logger.error(f"lun is not on this device {disk_wwn_info}")
                continue
            disk_size = int(int(lun_info.capacity) / 2)
            disk_path = "Disk" + disk_wwn_info.num
            group_name = OracleResourceService.get_disk_group_name_in_windows_os(asm_disk_infos, disk_path)
            logger.info(f"check_disk_path {disk_path} asm_group {group_name}")
            disk_info = {"disk_path": disk_path, "disk_size": disk_size, 'asm_group': group_name}
            disk_infos.append(disk_info)

        logger.info(f"check_disk_infos {disk_infos}")
        return disk_infos

    @staticmethod
    def get_disk_info_by_asm_tool_in_windows_os():
        logger.info(f"get_disk_info_by_asm_tool_in_windows_os")
        cmd = 'asmtool -list'
        ret, std_out = PowershellService.exec_pw_shell(cmd)
        asm_disk_infos = {}
        if not ret:
            logger.error("Get asm disk path FAILED! output = %s", std_out)
            return asm_disk_infos
        for line in std_out.split('\n'):
            logger.info(f"check_asm_tool_std_out_line {line}")
            if not line:
                continue
            asm_disk_infos[line.split()[1]] = line.split()[0]

        logger.info(f"check_asm_disk_infos {asm_disk_infos}")
        return asm_disk_infos

    @staticmethod
    def get_disk_group_name_in_windows_os(asm_disk_infos, disk_path):
        group_name = ''
        for disk_info, asm_disk_path in asm_disk_infos.items():
            if disk_path.lower() in disk_info:
                logger.info(f"get_disk_group_name_in_windows_os")
                group_name = OracleResourceService.query_disk_group_name_in_windows_os(asm_disk_path)
                break

        return group_name

    @staticmethod
    def query_disk_group_name_in_windows_os(asm_disk_path):
        group_name = ''
        logger.info(f"query_disk_group_name_in_windows_os")
        cmd = [f"kfed read //./{asm_disk_path}", "findstr grpname"]
        logger.info("query_disk_group_name_in_windows_os cmd = %s", cmd)
        ret, std_out, std_err = execute_cmd_list(cmd)
        if not ret:
            logger.error("Get disk group name FAILED! output = %s", std_err)
            return group_name
        logger.info("Get disk group name SUCCEED! output = %s", std_out)
        if std_out and len(std_out.split()) > 1:
            group_name = std_out.split()[1]

        return group_name

    @staticmethod
    def get_disk_info_in_linux_os(storage, device_manager, is_asm, db_install_user):
        logger.info(f"get disk info in linux os is_asm {is_asm}")
        disk_wwn_infos = get_all_lun_wwn_info_in_linux_os(storage)
        disk_infos = []
        for disk_wwn_info in disk_wwn_infos:
            lun_info = get_lun_info_by_wwn(disk_wwn_info.wwn, device_manager)
            if not lun_info:
                logger.warning(f"lun is not on this device {disk_wwn_info}")
                continue
            disk_size = int(int(lun_info.capacity) / 2)
            disk_info = {
                'disk_path': disk_wwn_info.disk_path,
                'disk_size': disk_size,
            }
            disk_uuid = get_disk_uuid(disk_wwn_info.disk_path)
            disk_info['disk_uuid'] = disk_uuid
            group_name = get_asm_group_name(disk_wwn_info.disk_path, db_install_user)
            disk_info['asm_group'] = group_name
            disk_infos.append(disk_info)
        return disk_infos

    @staticmethod
    def clean_env_when_remove_resource(pid):
        logger.info(f'pid: {pid}, start to clean env when remove resource')
        if platform.system().lower() == "windows":
            file_content = ParamFileUtil.parse_param_windows_file(pid)
        else:
            file_content = ParamFileUtil.parse_param_file(pid)
        response = ActionResult(code=ExecuteResultEnum.SUCCESS.value)
        protect_group_name = f'protect_group_{file_content.get("application", {}).get("id", "")}'
        storages = file_content['application']['auth']['extendInfo'].get('storages', [])
        storages = json.loads(storages)
        if not storages:
            output_result_file(pid, response.dict(by_alias=True))
            return True
        for index, storage in enumerate(storages):
            password = json.loads(get_env_variable(f"application_auth_extendInfo_storagesPwd_{pid}"))[index]
            ret, device_manager = check_storage_device_access(storage, password, pid)
            if not ret:
                logger.warn(f"check_storage_device_access failed, {storage.get('ipList')}")
                continue
            protect_group_info = ProtectGroupInfo()
            device_manager.query_protect_group(protect_group_name, protect_group_info)
            # 如果不存在保护组，返回
            if not protect_group_info.protect_group_id:
                continue
            consistency_groups = []
            device_manager.query_snapshot_consistency_groups_by_protect_group_id(protect_group_info.protect_group_id,
                                                                                 consistency_groups)
            if len(consistency_groups) > 0:
                # 删除快照
                for snap in consistency_groups:
                    device_manager.delete_snapshot_consistency_group(snap.group_id)
            # 判断保护组里是否存在LUN，存在则移除
            ret = OracleResourceService.remove_lun_from_protect_group(
                device_manager, protect_group_info.protect_group_id)
            if not ret:
                response.code = ExecuteResultEnum.INTERNAL_ERROR.value
                output_result_file(pid, response.dict(by_alias=True))
                logger.error(f'pid: {pid}, remove lun from protect group failed')
                return False
            error_code, error_desc = device_manager.delete_protect_group(protect_group_info.protect_group_id)
            if error_code != ReturnCode.SUCCESS:
                response.code = ExecuteResultEnum.INTERNAL_ERROR.value
                output_result_file(pid, response.dict(by_alias=True))
                logger.error(f'pid: {pid}, delete protect group failed')
                return False
        output_result_file(pid, response.dict(by_alias=True))
        logger.info(f'pid: {pid}, end to clean env when remove resource')
        return True

    @staticmethod
    def remove_lun_from_protect_group(device_manager, protect_group_id):
        lun_infos = []
        device_manager.query_lun_in_protect_group(protect_group_id, lun_infos)
        if len(lun_infos) == 0:
            return True
        lun_ids = []
        for lun in lun_infos:
            lun_ids.append(lun.lun_id)
        error_code, error_desc = device_manager.delete_lun_from_protect_group(lun_ids, protect_group_id)
        if error_code != ReturnCode.SUCCESS:
            return False
        return True

    @staticmethod
    def delete_archive_log(pid, end_scn, params):
        instance_name = params.get("instance_name", "")
        logger.info(f"delete_archive_log start! pid {pid}, end_scn {end_scn}")
        ret_code, archive_dest_list = get_archive_dest_list(pid, params)
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Get archive dest list failed.")
            return ret_code
        if platform.system().lower() == Platform.WINDOWS:
            tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/delete_archive_log_{pid}.sql"
            tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/delete_archive_log_{pid}.txt"
            content = ""
            for arch_dest in archive_dest_list:
                if arch_dest.startswith("+", SliceConstant.HEAD_OF_STRING_OR_LIST):
                    content += f"delete force noprompt archivelog until scn {end_scn} like '{arch_dest}/%';\n".upper()
                else:
                    content += f"delete force noprompt archivelog until scn {end_scn} like '{arch_dest}\\%';\n".upper()
        else:
            tmp_sql = f"{PluginPathConstant.STMP_PATH}/delete_archive_log_{pid}.sql"
            tmp_rst = f"{PluginPathConstant.STMP_PATH}/delete_archive_log_{pid}.txt"
            content = ""
            for arch_dest in archive_dest_list:
                content += f"delete force noprompt archivelog until scn {end_scn} like '{arch_dest}%';\n"
        content += "exit;"
        write_tmp_file(tmp_sql, content)

        logger.info("Exec RMAN to delete archivelog of database.")
        if platform.system().lower() == Platform.WINDOWS:
            ret = execute_windows_rman_cmd(pid, params, 0, tmp_sql, tmp_rst)
        else:
            touch_file(tmp_rst)
            chown_file_path_owner(tmp_rst, params.get("db_install_user", ""))
            chown_file_path_owner(tmp_sql, params.get("db_install_user", ""))
            ret = execute_linux_rman_cmd(pid, params, 0, tmp_sql, tmp_rst)
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        if ret != ScriptExitCode.SUCCESS:
            logger.error(f"Delete archivelog of database {instance_name} failed, pid:{pid}.")
            if ret == ScriptExitCode.ERROR_SCRIPT_EXEC_FAILED:
                return ScriptExitCode.ERROR_ORACLE_TRUNCATE_ARCHIVELOG_FAILED
            else:
                return ret
        logger.info(f"Delete archivelog of database {instance_name} succ. pid:{pid}.")
        return ScriptExitCode.SUCCESS

    @staticmethod
    def clear_archive_log(pid):
        logger.info(f"clear_archive_log start. pid:{pid}.")
        if platform.system().lower() == Platform.WINDOWS:
            file_content = ParamFileUtil.parse_param_windows_file(pid)
        else:
            file_content = ParamFileUtil.parse_param_file(pid)

        app_extend_info = file_content['application'].get('extendInfo', {})
        install_user_name = app_extend_info.get('installUsername', '')
        extend_info = file_content.get('extendInfo', {})
        logger.info(f"clear_archive_log extend_info {extend_info}.")
        db_instance_name = OracleResourceService.get_instance_name(file_content['application'].get('subType'),
                                                                   app_extend_info, extend_info)

        db_password = get_env_variable(f'application_auth_authPwd_{pid}')
        db_user = get_env_variable(f'application_auth_authKey_{pid}')
        params = {
            'instance_name': db_instance_name,
            'db_user': db_user,
            'db_password': db_password,
            'db_install_user': install_user_name
        }
        oracle_base, oracle_home = OracleResourceService.get_oracle_base_and_home(file_content)
        get_user_shell_type(install_user_name)
        if platform.system().lower() != Platform.WINDOWS and oracle_home:
            update_oracle_env(oracle_base=oracle_base, oracle_home=oracle_home)
        delete_before_time = extend_info.get('delete_before_time', '')
        end_scn, err_msg = OracleResourceService.get_scn_before_time(pid, params, extend_info, delete_before_time)
        if not end_scn:
            logger.warn(f"get_scn_before_time is null, err_msg {err_msg}.")
            if err_msg:
                response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value)
                response.message = err_msg
            else:
                response = ActionResult(code=ExecuteResultEnum.SUCCESS.value)
            output_result_file(pid, response.dict(by_alias=True))
            return
        params['db_password'] = get_env_variable(f'application_auth_authPwd_{pid}')
        ret_code = OracleResourceService.delete_archive_log(pid, end_scn, params)
        if ret_code != ScriptExitCode.SUCCESS:
            response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value)
            response.message = 'delete archivelog failed'
            output_result_file(pid, response.dict(by_alias=True))
            return
        response = ActionResult(code=ExecuteResultEnum.SUCCESS.value)
        output_result_file(pid, response.dict(by_alias=True))
        return

    @staticmethod
    def get_scn_before_time(pid, params, extend_info, delete_before_time):
        end_scn = extend_info.get('endSCN', '')
        if delete_before_time and delete_before_time != '0':
            before_time = OracleResourceService.get_delete_before_time(extend_info, delete_before_time)
            scn_before_time, err_msg = get_first_scn_contains_time(pid, params, before_time)
            if not scn_before_time:
                logger.warn(f"get_first_scn_contains_time is null, err_msg {err_msg}.")
                return scn_before_time, err_msg
            # 取scn_before_time 和 end_scn 的最小值作为删除的截止scn
            logger.info(f"scn_before_time is {scn_before_time} end_scn is {end_scn}.")
            end_scn = scn_before_time if scn_before_time < end_scn else end_scn
        params['db_password'] = get_env_variable(f'application_auth_authPwd_{pid}')
        end_scn, err_msg = get_last_archive_log_next_change(pid, params, end_scn)
        return end_scn, err_msg

    @staticmethod
    def get_delete_before_time(extend_info, delete_before_time):
        delete_before_time_unit = extend_info.get('delete_before_time_unit', '')
        time_unit = TimeUnit.TIME_UNIT_DICT.get(delete_before_time_unit, '')
        delete_before_time = int(delete_before_time) * time_unit
        end_time = extend_info.get('endTime', '')
        format_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(end_time)))
        end_time = datetime.datetime.strptime(format_time, "%Y-%m-%d %H:%M:%S")
        # 计算endTime之前delete_before_time的时间
        before_time = end_time - datetime.timedelta(hours=int(delete_before_time))
        return before_time

    @staticmethod
    def get_instance_name(subtype, app_extend_info, extend_info):
        db_instance_name = ''
        if subtype == ListAppQueryConstant.ORACLE_CLUSTER:
            agent_id = extend_info.get('agentId')
            instances = json.loads(app_extend_info.get('instances', '{}'))
            for instance in instances:
                if instance.get('hostId') == agent_id:
                    db_instance_name = instance.get('inst_name', '')
                    break
        else:
            db_instance_name = app_extend_info.get('inst_name', '')
        return db_instance_name

    @staticmethod
    def merge_pdbs(users_map, user_name, pdb_result):
        cur_user = users_map.get(user_name)
        cur_pdb_name = pdb_result.get("pdb_name")
        if not cur_user:
            users_map[user_name] = {
                "user_name": user_name,
                "pdbs": [
                    {
                        "pdb_name": cur_pdb_name,
                        "table_spaces": pdb_result.get("table_spaces"),
                    }
                ]
            }
        else:
            # 找下列表里有没有当前pdb
            pdbs = cur_user.get("pdbs")
            for pdb in pdbs:
                # 有则合并
                if pdb.get("pdb_name") == cur_pdb_name:
                    pdb["table_spaces"] = list(set(pdb.get("table_spaces") + pdb_result.get("table_spaces")))
                    return
            # 没有则添加
            pdbs.append({
                "pdb_name": cur_pdb_name,
                "table_spaces": pdb_result.get("table_spaces"),
            })

    @staticmethod
    def check_cdb_pdb(pid, resources, install_user_name):
        if resources:
            login_params = {
                'instance_name': resources[0].extend_info.get('inst_name', ''),
                'db_install_user': install_user_name,
                'auth_pwd': f"application_auth_authPwd_{pid}",
                'auth_key': f"application_auth_authKey_{pid}"
            }
            cdb_is_open = check_cdb_is_open(pid, login_params)
            resources[0].extend_info['is_cdb'] = str(cdb_is_open).lower()

    @staticmethod
    def check_cdb_pdb_windows(pid, resource, install_user_name):
        if resource:
            login_params = {
                'instance_name': resource.extend_info.get('inst_name', ''),
                'db_install_user': install_user_name,
                'auth_pwd': f"application_auth_authPwd_{pid}",
                'auth_key': f"application_auth_authKey_{pid}"
            }
            cdb_is_open = check_cdb_is_open(pid, login_params)
            resource.extend_info['is_cdb'] = str(cdb_is_open).lower()

    @staticmethod
    def check_database_open_mode(pid, resources, install_user_name):
        if resources:
            for resource in resources:
                login_params = {
                    'instance_name': resources[0].extend_info.get('inst_name', ''),
                    'db_install_user': install_user_name,
                    'auth_pwd': f"application_auth_authPwd_{pid}",
                    'auth_key': f"application_auth_authKey_{pid}"
                }
                open_mode = check_database_open_mode(pid, login_params)
                resource.extend_info["open_mode"] = str(open_mode).lower()

    @staticmethod
    def check_database_open_mode_windows(pid, resource, install_user_name):
        if resource:
            login_params = {
                'instance_name': resource.extend_info.get('inst_name', ''),
                'db_install_user': install_user_name,
                'auth_pwd': f"application_auth_authPwd_{pid}",
                'auth_key': f"application_auth_authKey_{pid}"
            }
            open_mode = check_database_open_mode(pid, login_params)
            resource.extend_info['open_mode'] = str(open_mode).lower()
